package site.teamo.biu.net.client.core.http_https_proxy;

import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import site.teamo.biu.net.client.entity.ConnectionRuleDO;
import site.teamo.biu.net.common.http_https_proxy.constant.ActionEnum;
import site.teamo.biu.net.common.http_https_proxy.constant.MethodEnum;
import site.teamo.biu.net.common.util.Domain2IpSearcher;
import site.teamo.biu.net.common.util.HostPort;
import site.teamo.biu.net.common.util.Ip2RegionSearcher;
import site.teamo.biu.tool.spring.util.SpringEnvironmentHolder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * @author haocongshun
 * @date 2023/03/03 11:23:00
 */
@Slf4j
public class ActionClassifier {

    private final Map<String, ActionEnum> actionCache;

    private final Map<String, Decider> deciders;

    private final ActionEnum defaultAction;

    @Getter
    private Domain2IpSearcher domain2IpSearcher;

    private Ip2RegionSearcher ip2RegionSearcher;

    public ActionClassifier(List<ConnectionRuleDO> rules,
                            ActionEnum defaultAction) {
        actionCache = new HashMap<>();
        deciders = new ConcurrentHashMap<>();
        loadRules(rules);
        this.defaultAction = defaultAction;
        try {
            Environment environment = SpringEnvironmentHolder.getEnvironment();
            String[] dns = environment.getProperty("biu-net.classifier.dns", "").split(",");
            this.domain2IpSearcher = Domain2IpSearcher.create(dns);
            String ip2regionDbPath = environment.getProperty("biu-net.classifier.ip2region-file");
            this.ip2RegionSearcher = Ip2RegionSearcher.create(ip2regionDbPath);
        } catch (Exception e) {
            log.warn("域名转ip地域分类方法使用失败", e);
        }
    }

    public void loadRules(List<ConnectionRuleDO> rules) {
        deciders.clear();
        for (ConnectionRuleDO rule : rules) {
            deciders.put(rule.getId(), new Decider(rule));
        }
    }

    public ActionEnum classify(HostPort hostPort) {
        //在缓存中查找是否连接策略
        ActionEnum action = actionCache.get(hostPort.host());
        if (action != null) {
            return action;
        }
        //根据规则来判定连接策略
        for (Decider decider : deciders.values()) {
            action = decider.doDecide(hostPort.host());
            if (action != null) {
                actionCache.put(hostPort.host(), action);
                return action;
            }
        }
        try {
            action = classifyByRegion(hostPort.host());
        } catch (Exception e) {
            //未找到对应规则连接策略时，则适用默认连接策略
            action = defaultAction;
        }
        actionCache.put(hostPort.host(), action);
        return action;
    }

    private ActionEnum classifyByRegion(String host) throws Exception {
        String ip = domain2IpSearcher.search(host);
        String region = ip2RegionSearcher.search(ip);
        if (StrUtil.equals("中国", region)) {
            return ActionEnum.DIRECT;
        }
        return defaultAction;
    }

    private static class Decider {
        private final Function<String, Boolean> function;

        private final ActionEnum action;

        public Decider(ConnectionRuleDO rule) {
            this.action = rule.getAction();
            this.function = generateFunction(rule.getMethod(), rule.getContent());
        }

        public ActionEnum doDecide(String target) {
            if (function.apply(target)) {
                return action;
            }
            return null;
        }

        private static Function<String, Boolean> generateFunction(MethodEnum method, String content) {
            return switch (method) {
                case EQUALS -> target -> StrUtil.equalsIgnoreCase(target, content);
                case CONTAINS -> target -> StrUtil.containsIgnoreCase(target, content);
                case START_WITH -> target -> StrUtil.startWithIgnoreCase(target, content);
                case END_WITH -> target -> StrUtil.endWithIgnoreCase(target, content);
            };
        }
    }
}
